Skip to content

refactor(forks): tighten ForkProtocol surface (Stage 1 of #686)#694

Merged
tcoratger merged 3 commits into
leanEthereum:mainfrom
tcoratger:stage1-tighten-fork-protocol
Apr 30, 2026
Merged

refactor(forks): tighten ForkProtocol surface (Stage 1 of #686)#694
tcoratger merged 3 commits into
leanEthereum:mainfrom
tcoratger:stage1-tighten-fork-protocol

Conversation

@tcoratger
Copy link
Copy Markdown
Collaborator

Part of #686 — Stage 1 only. Other stages (2–8) remain in scope of the tracking issue.

Summary

Tightens the ForkProtocol surface so subclass-and-narrow inheritance ("Vision B") works under pyright/ty invariance rules, and removes the silent fork bypass in pre-state generation.

Type tightening

  • Drop Any from ForkProtocol method signatures. Replace with Uint64 and structural Protocols.
  • Add SpecBlockType Protocol so block_class is no longer typed type (i.e. type[Any]).
  • Drop ClassVar from state_class / block_class / store_class. ClassVar triggers the type checker's invariance rule and would block a future Devnet5Spec(LstarSpec) from declaring state_class: type[Devnet5State] = Devnet5State. ClassVar stays on true constants (NAME, VERSION, GOSSIP_DIGEST, previous).
  • Rename NETWORK_NAMEGOSSIP_DIGEST. The field already served the gossipsub fork-digest role; the rename matches the intent.

generate_pre_state fork fallback

  • Remove the fork: ForkProtocol | None = None branch that called State.generate_genesis(...) directly when fork was None. Every call now dispatches through fork.generate_genesis(...).
  • The pre pytest fixture (packages/testing/.../filler.py) now requests the framework's fork fixture and threads fork.spec_class()() through.
  • Direct callers in fixture builders (sync.py, api_endpoint.py) pass LstarSpec() explicitly, consistent with their existing lstar coupling.

Honest casts at concrete-fork boundaries

fork.generate_genesis(...) and fork.create_store(...) return Protocol-typed values (SpecStateType / SpecStoreType). The two callers that need narrow access (__main__.py, node.py) use cast(State, ...) / cast(Store, ...) at the boundary — no # type: ignore needed. The lstar fork class deliberately does not override these methods to "narrow" the return type, since doing so would require # type: ignore[arg-type] to bridge the LSP-widened input back to the concrete narrow signature. A clean cast at the boundary is more honest than an ignore in the spec class.

Stage 1 checklist (from #686)

  • Replace Any in ForkProtocol method signatures with fork-stable primitives and structural protocols.
  • Add SpecBlockType Protocol so block_class stops being typed type.
  • Drop ClassVar from state_class / block_class / store_class.
  • Add GOSSIP_DIGEST: ClassVar[str]; route consumers through fork.GOSSIP_DIGEST.
  • Delete the generate_pre_state fork fallback. Thread fork through the pre pytest fixture.

The remaining Stage 1 items (use DEFAULT_REGISTRY in __main__.py, add previous, rename SpecRunnerForkRegistry, make upgrade_state abstract, direct bindings on devnet5/spec.py) were already merged in earlier PRs.

Test plan

  • uvx tox -e all-checks passes (ruff, format, ty, codespell, mdformat).
  • uv run pytest --no-cov passes.
  • uv run fill --fork=lstar --clean -n auto generates fixtures.

🤖 Generated with Claude Code

tcoratger and others added 3 commits April 30, 2026 16:58
Drop Any from ForkProtocol method signatures. Replace with fork-stable
primitives (Uint64) and structural protocols (SpecStateType, SpecBlockType,
SpecStoreType). Drop ClassVar from state_class/block_class/store_class so
subclasses can narrow them under pyright/ty invariance rules.

Rename NETWORK_NAME to GOSSIP_DIGEST: the field already serves the
gossipsub fork-digest role.

Remove the None-fork fallback in generate_pre_state. Every call now
dispatches through fork.generate_genesis. The pre pytest fixture threads
fork from the framework's fork fixture.

Refs leanEthereum#686

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
object was too loose for the structural validators parameter on
generate_genesis. SSZList is the fork-stable concrete base; the element
type stays generic because each fork owns its own Validator class.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tcoratger tcoratger merged commit 9bca4b3 into leanEthereum:main Apr 30, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant